package cn.kennylee.codehub.common.das;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 抽象查询策略
 * <p>Created on 2024/12/11.</p>
 *
 * @param <E> 查询条件dto，属性名应为根据entity的属性名+操作符
 * @param <T> entity实体类
 * @param <Q> 对应数据库的查询条件容器
 * @author kennylee
 * @since 0.0.1
 */
@Getter
@Setter
@Slf4j
public abstract class AbstractDasQueryStrategy<E, T, C, Q> implements DasQueryStrategy<E, T> {

    protected Class<T> entityClass;

    @Override
    public Q parseQuery(E searchExample, Class<T> entityClass, Collection<String> ignoreProps) {
        this.entityClass = entityClass;

        Map<String, List<QueryCriteriaDto>> queryCriteriaDtos = parseQueryCriteriaDtos(searchExample, entityClass, ignoreProps);
        return parseQueryCriteriaDtos(getQuery(), queryCriteriaDtos);
    }

    /**
     * 新建查询条件，一个prop名字对应一个Criteria
     *
     * @param propName 属性名
     * @return 单个查询条件
     */
    protected abstract C newCriteria(String propName);

    /**
     * Query包含Criteria
     *
     * @return 查询条件容器
     */
    protected abstract Q getQuery();

    /**
     * 把查询条件添加到查询条件容器
     *
     * @param query               查询条件容器
     * @param queryCriteriaDtoMap 查询条件，key为属性名，value为查询条件列表
     * @return 查询条件容器
     */
    protected Q parseQueryCriteriaDtos(Q query, Map<String, List<QueryCriteriaDto>> queryCriteriaDtoMap) {
        queryCriteriaDtoMap.forEach((entityPropName, queryCriteriaDtos) -> {
            addCriteria(convertDbPropName(entityPropName), queryCriteriaDtos);
        });
        return query;
    }

    /**
     * 转换entity属性名为数据库属性名
     *
     * @param entityPropName entity属性名
     * @return 数据库属性名
     */
    protected String convertDbPropName(String entityPropName) {
        return entityPropName;
    }

    /**
     * 添加查询条件
     *
     * @param propName          属性名
     * @param queryCriteriaDtos 查询条件列表
     * @return 查询条件
     */
    protected C addCriteria(String propName, List<QueryCriteriaDto> queryCriteriaDtos) {

        final C criteria = newCriteria(propName);

        for (QueryCriteriaDto queryCriteriaDto : queryCriteriaDtos) {
            CondOperation condOperation = queryCriteriaDto.getCondOperation();
            Object val = queryCriteriaDto.getValue();

            switch (condOperation) {
                case EQ:
                    addEqCriteria(criteria, propName, val);
                    break;
                case NE:
                    addNeCriteria(criteria, propName, val);
                    break;
                case GT:
                    addGtCriteria(criteria, propName, val);
                    break;
                case GE:
                    addGeCriteria(criteria, propName, val);
                    break;
                case LT:
                    addLtCriteria(criteria, propName, val);
                    break;
                case LE:
                    addLeCriteria(criteria, propName, val);
                    break;
                case LIKE:
                    addLikeCriteria(criteria, propName, val);
                    break;
                case IN:
                    if (val instanceof Collection<?> vals) {
                        addInCriteria(criteria, propName, vals);
                    } else {
                        log.warn("IN操作符的值不是集合或数组：{}", val);
                    }
                    break;
                case NOT_IN:
                    if (val instanceof Collection<?> vals) {
                        addNotInCriteria(criteria, propName, vals);
                    } else {
                        log.warn("NOT IN操作符的值不是集合或数组：{}", val);
                    }
                    break;
                case PRE_LIKE:
                    addPreLikeCriteria(criteria, propName, val);
                    break;
                case SUF_LIKE:
                    addSufLikeCriteria(criteria, propName, val);
                    break;
                case NOT_LIKE:
                    addNotLikeCriteria(criteria, propName, val);
                    break;
                case IS_NULL:
                    addIsNullCriteria(criteria, propName);
                    break;
                case IS_NOT_NULL:
                    addIsNotNullCriteria(criteria, propName);
                    break;
                default:
                    log.warn("未知的操作符：{}", condOperation);
            }
        }

        return criteria;
    }

    /**
     * 添加不等于null的查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     */
    protected abstract void addIsNotNullCriteria(C criteria, String propName);

    /**
     * 添加等于null的查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     */
    protected abstract void addIsNullCriteria(C criteria, String propName);

    /**
     * 添加不模糊查询查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addNotLikeCriteria(C criteria, String propName, Object val);

    /**
     * 添加后缀模糊查询查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addSufLikeCriteria(C criteria, String propName, Object val);

    /**
     * 添加前缀模糊查询查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addPreLikeCriteria(C criteria, String propName, Object val);

    /**
     * 添加不包含列表中的查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param vals     查询值列表
     */
    protected abstract void addNotInCriteria(C criteria, String propName, Collection<?> vals);

    /**
     * 添加包含列表中的查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param vals     查询值列表
     */
    protected abstract void addInCriteria(C criteria, String propName, Collection<?> vals);

    /**
     * 添加模糊查询查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addLikeCriteria(C criteria, String propName, Object val);

    /**
     * 添加大于等于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addLeCriteria(C criteria, String propName, Object val);

    /**
     * 添加小于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addLtCriteria(C criteria, String propName, Object val);

    /**
     * 添加大于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addGeCriteria(C criteria, String propName, Object val);

    /**
     * 添加等于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addEqCriteria(C criteria, String propName, Object val);

    /**
     * 添加不等于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addNeCriteria(C criteria, String propName, Object val);

    /**
     * 添加大于查询条件
     *
     * @param criteria 查询条件
     * @param propName 属性名
     * @param val      查询值
     */
    protected abstract void addGtCriteria(C criteria, String propName, Object val);

    /**
     * 忽略的查询条件属性名列表
     *
     * @return 忽略的查询条件属性名列表
     */
    protected Set<String> ignoreSearchProps() {
        return Collections.emptySet();
    }

    /**
     * 解析查询条件
     *
     * @param searchExample 查询实例
     * @param entityClass   entity实体类
     * @param ignoreProps   忽略的属性列表
     * @return 查询条件列表
     */
    protected Map<String, List<QueryCriteriaDto>> parseQueryCriteriaDtos(E searchExample, Class<T> entityClass, Collection<String> ignoreProps) {

        // key为entity的属性名+操作符，value为查询值
        final Map<String, Object> notNullPropsMap = getNotNullPropsMap(searchExample, ignoreProps);

        // 根据eo，获取属性名列表，驼峰风格
        final Set<String> eoProps = Arrays.stream(ReflectUtil.getFields(entityClass))
            .map(it -> StrUtil.toCamelCase(it.getName()))
            .collect(Collectors.toSet());

        final MultiValueMap<String, QueryCriteriaDto> queryCriteriaDtos = new LinkedMultiValueMap<>();

        notNullPropsMap.forEach((searchPropName, value) -> {

            if (log.isDebugEnabled()) {
                log.debug("添加查询条件，属性名：{}，属性值：{}", searchPropName, value);
            }
            CondOperation condOperation = CondOperation.match(searchPropName);

            String entityPropName = condOperation.subFieldNameCamelCase(searchPropName);

            if (false == eoProps.contains(entityPropName)) {
                log.debug("entity属性名不存在：{}", entityPropName);
                return;
            }

            if (value instanceof Iterable<?> iterableVal) {
                if (CollUtil.isEmpty(iterableVal)) {
                    log.debug("查询值列表内容为空，忽略，属性名：{}", entityPropName);
                    return;
                }
            }

            final QueryCriteriaDto queryCriteriaDto = QueryCriteriaDto.builder()
                .entityPropName(entityPropName).condOperation(condOperation).value(value).build();

            if (log.isDebugEnabled()) {
                log.debug("添加查询条件：{}", queryCriteriaDto);
            }

            queryCriteriaDtos.add(entityPropName, queryCriteriaDto);
        });

        return queryCriteriaDtos;
    }

    /**
     * 获取查询dto中的非空属性
     *
     * @param searchExample 查询实例
     * @param ignoreProps   忽略的属性列表
     * @return 有值的属性，key，驼峰风格
     */
    protected Map<String, Object> getNotNullPropsMap(E searchExample, Collection<String> ignoreProps) {

        if (searchExample instanceof Map<?, ?> searchCriteriaMap) {
            // map的key，转驼峰风格，返回
            return searchCriteriaMap.entrySet().stream()
                .collect(Collectors.toMap(entry -> StrUtil.toCamelCase(entry.getKey().toString()), Map.Entry::getValue));
        }
        Set<String> allIgnoreProps = new HashSet<>(ignoreSearchProps());
        if (CollUtil.isNotEmpty(ignoreProps)) {
            allIgnoreProps.addAll(ignoreProps);
        }
        return BeanUtil.beanToMap(searchExample, new LinkedHashMap<>(), CopyOptions.create()
            .ignoreNullValue().setIgnoreProperties(allIgnoreProps.toArray(new String[0])));
    }
}
